feat(ui): add Mermaid diagram visualization in markdown#21497
feat(ui): add Mermaid diagram visualization in markdown#21497callmeYe wants to merge 10 commits into
Conversation
Add interactive Mermaid diagram rendering for ```mermaid code blocks, inspired by Vercel's @streamdown/mermaid package. Features: - Lazy-load mermaid library (~3MB) on first use - Auto-detect dark/light theme for diagram rendering - Interactive controls: zoom (0.5x-3x), pan (drag), fullscreen, download (SVG/PNG/source), copy source - Fullscreen mode re-renders SVG to avoid DOM ID conflicts - Fix SVG width="100%" collapse in flex layouts via viewBox sizing - Streaming-compatible: incomplete code fences show raw code - Error handling with source fallback display Integration: - Intercept mermaid language in markedShiki highlight and highlightCodeBlocks (native parser path) - Skip mermaid blocks in ensureCodeWrapper (decorate) - Preserve rendered diagrams across morphdom updates - Async post-processing after morphdom Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
533e0c6 to
10e6c50
Compare
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
Merged latest dev into feat/mermaid-upstream and regenerated bun.lock via `bun install` to resolve the lockfile conflict.
|
/review |
The previous bun install runs on this branch picked up a local ~/.npmrc that points to the Alibaba intranet mirror (registry.anpm.alibaba-inc.com), which baked private registry URLs into ~3257 package entries in bun.lock. That makes the lockfile unreproducible for anyone outside the corp network and breaks upstream CI. Reset bun.lock to github/dev's baseline and re-ran `BUN_CONFIG_REGISTRY=https://registry.npmjs.org/ bun install` so the new mermaid dependency tree is added against the public registry. The only remaining diff vs dev is the mermaid packages plus the version bumps brought in by the merge from dev. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Hey @adamdotdevin, could you review this PR when you get a chance? It adds Mermaid diagram rendering for |
|
Automated PR Cleanup Thank you for contributing to opencode. Due to the high volume of PRs from users and AI agents, we periodically close older PRs using automated criteria so maintainers can focus review time on the most active and community-supported contributions. This PR was closed because it matched the following cleanup criteria:
PRs created within the last month are not affected by this cleanup. If you believe this PR was closed incorrectly, or if you are still actively working on it, please leave a comment explaining why it should be reopened. A maintainer can review and reopen it if appropriate. Thanks again for taking the time to contribute. |
|
Ran this branch locally against streaming markdown from a chat model and hit three correctness issues that don't show up in static-file tests. Filing them together since they share root causes (treating each container as "render-once" and missing concurrency guards). 1. Re-render is skipped after the first chunk (streaming use case)
const containers = root.querySelectorAll(
'[data-component="mermaid-diagram"]:not([data-mermaid-rendered])',
)…and both Repro: the model streams a mermaid block in chunks. Chunk 1 arrives as Suggested fix: key the skip on source identity, not a boolean flag. Hash the source and store it on two attributes: const MERMAID_SOURCE_ATTR = "data-mermaid-source-hash"
const MERMAID_LAST_GOOD_HASH = "data-mermaid-last-good-hash"
// Skip if last successful render matches current source.
// Retry if only a failed attempt is recorded — source may now be complete.
if (container.getAttribute(MERMAID_LAST_GOOD_HASH) === sourceHash) continue
if (container.getAttribute(MERMAID_SOURCE_ATTR) === sourceHash) continue
container.setAttribute(MERMAID_SOURCE_ATTR, sourceHash) // mark attempt
// ...render...
container.setAttribute(MERMAID_LAST_GOOD_HASH, sourceHash) // mark successOn failure, the existing rendered DOM (from the previous good chunk) stays visible while the model keeps streaming. 2.
|
|
If there's still appetite from the maintainer side to land this, I'm happy to keep iterating @rekram1-node @rasperepodvipodvert |
Issue for this PR
Closes #
Type of change
What does this PR do?
Adds interactive Mermaid diagram visualization for
```mermaidcode blocks in the markdown renderer, inspired by Vercel's @streamdown/mermaid.Currently, mermaid code blocks are rendered as plain text (Shiki doesn't recognize the mermaid language and falls back to text). This PR intercepts mermaid code blocks in both
markedShikihighlight andhighlightCodeBlocks(native parser path), outputs a placeholder<div data-component="mermaid-diagram">, then asynchronously renders them as interactive SVG diagrams after morphdom DOM updates.The mermaid library (~3MB) is lazy-loaded on first use to avoid impacting initial bundle size.
Interactive features:
setPointerCapturefor reliable trackingdocument.body, re-renders SVG viamermaid.render()with a fresh ID to avoid DOM ID/CSS selector conflicts with the inline copy. Closes via Escape key or X button..mmdsource exportStreaming compatibility:
markdown-stream.tsalready splits incomplete code fences, so incomplete mermaid blocks display as raw code until the fence closes, then auto-render. Rendered diagrams are preserved acrossmorphdomupdates viaonBeforeElUpdatedreturning false fordata-mermaid-renderedelements.Key fix: Mermaid outputs
width="100%"on SVGs, which collapses to 0px in a pure flex layout (the fullscreen viewport).fixSvgSizing()replaces percentage width with the actual viewBox dimensions and constrains withmax-width: 90vw/max-height: 85vh.How did you verify your code works?
bun run typecheckinpackages/ui)Screenshots / recordings
N/A — this is best tested interactively. Send any message containing a
```mermaidcode block to see the rendered diagram with hover controls.Checklist
🤖 Generated with Claude Code